#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <tool/yhtml.h>

/*  http://www.w3schools.com/tags/ref_entities.asp
HTML 4.01 Entities Name & Entities Number
Entities Name - longest length sample "&plusmn;"
Entity Number - longest length sample "&#8364;" 
i.e.  "12345678"
      "01234567"
      "&plusmn;"
      "&#8364;" 
*/
#ifdef  HTML_ENTITY_STR_MIN_LEN
#undef  HTML_ENTITY_STR_MIN_LEN
#endif
/** Entities Name & Number - shortest length sample
<br/>Entity Name "&lt;" less-than
<br/>Entity Number "&#34;" quotation mark
*/
#define HTML_ENTITY_STR_MIN_LEN   4
#ifdef  HTML_ENTITY_NAME_MAX_LEN
#undef  HTML_ENTITY_NAME_MAX_LEN
#endif
/** Entities Name - longest length sample "&plusmn;" */
#define HTML_ENTITY_NAME_MAX_LEN  8
#ifdef  HTML_ENTITY_NUM_MAX_LEN
#undef  HTML_ENTITY_NUM_MAX_LEN
#endif
/** Entity Number - longest length sample "&#8364;" */
#define HTML_ENTITY_NUM_MAX_LEN   7
#ifdef  HTML_COMMEND_START
#undef  HTML_COMMEND_START
#endif
/** HTML Comment start tag */
#define HTML_COMMEND_START        "<!--"
#ifdef  HTML_COMMEND_START_LEN
#undef  HTML_COMMEND_START_LEN
#endif
/** Sample to HTML comment start tag length
<pre>
#define HTML_COMMEND_START        "<!--"
</pre>
*/
#define HTML_COMMEND_START_LEN    4
#ifdef  HTML_COMMEND_END
#undef  HTML_COMMEND_END
#endif
/** HTML Comment end tag */
#define HTML_COMMEND_END          "-->"
#ifdef  HTML_COMMEND_END_LEN
#undef  HTML_COMMEND_END_LEN
#endif
/** Sample to HTML comment start tag length
<pre>
#define HTML_COMMEND_START        "-->"
</pre>
*/
#define HTML_COMMEND_END_LEN      3

/** HTML function data struct */
struct htmlFnData {
  unsigned char sizeof_char;
  /** Type <code>strFn</code> */
  strFn strFn;
  /** If type <code>strFn</code> was pass in and 
    not created by library's function. */
  boolean setStrFn;
};

/*======= declare internal function */
boolean yhtml_setStrFn(htmlFn fn, strFn strFn);
boolean yhtml_freeFn(htmlFn *fnP);
/* specific operation */
boolean yhtml_getTagCotent(htmlFn fn,
    char *ptr, const unsigned int readLen, const char *tagWord, 
    char **contentStart, char **contentEnd, htmlTagMatch matchMethod);
boolean yhtml_isCommentTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    char *charPos, char **tagStartChar, char **tagEndChar);
boolean yhtml_isTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *charPos, char **tagStartChar, char **tagEndChar);
boolean yhtml_isStartTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *tagWord, const char *charPos, 
    char **tagStartChar, char **tagEndChar);
boolean yhtml_isEndTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *tagWord, const char *charPos, 
    char **tagStartChar, char **tagEndChar);
boolean yhtml_isTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    char *charPos, char **tagStartChar, char **tagEndChar);
boolean yhtml_isStartTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char *charPos, 
    char **tagStartChar, char **tagEndChar);
boolean yhtml_isEndTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char *charPos, 
    char **tagStartChar, char **tagEndChar);
boolean yhtml_isInCommentTag(htmlFn fn,
    char *ptr, const unsigned long readLen, char *charPos);
char *yhtml_getFirstStartTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar);
char *yhtml_getFirstEndTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar);
char *yhtml_getLastStartTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar);
char *yhtml_getLastEndTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar);
unsigned char yhtml_isHtmlEntityStr(const char *str, 
    const unsigned int strLen);
unsigned char yhtml_skipHtmlEntityStr(const char *str, 
    const unsigned int strLen);
/*--- function ONLY for internal use ---*/
boolean yhtml_initStrFn(htmlFn fn);

/*======= directly exposed function */
/** Create a <code>htmlFn</code> that provide yhtml function pointers.
@return the struct of yhtml function pointers
  or <code>NULL</code> if failure.
*/
htmlFn yhtml_createFn(void) {
  htmlFn fn;
  FN_MALLOC_ERRMSG(fn, sizeof(struct htmlFn), 
      "Error: yhtml_createFn(void) malloc(fn)")
  if (fn == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(fn->d, sizeof(struct htmlFnData), 
      "Error: yhtml_createFn(void) malloc(fn->d)")
  if (fn->d == NULL) {yhtml_freeFn(&fn); return NULL;}
  /*--- data ---*/
  fn->d->sizeof_char = sizeof(char);
  fn->d->strFn = NULL;
  fn->d->setStrFn = false;
  /*--- init function pointers ---*/
  fn->setStrFn = yhtml_setStrFn;
  fn->freeFn = yhtml_freeFn;
  /* specific operation */
  fn->getTagCotent = yhtml_getTagCotent;
  fn->isCommentTag = yhtml_isCommentTag;
  fn->isTag = yhtml_isTag;
  fn->isStartTag = yhtml_isStartTag;
  fn->isEndTag = yhtml_isEndTag;
  fn->isTagInAction = yhtml_isTagInAction;
  fn->isStartTagInAction = yhtml_isStartTagInAction;
  fn->isEndTagInAction = yhtml_isEndTagInAction;
  fn->isInCommentTag = yhtml_isInCommentTag;
  fn->getFirstStartTag = yhtml_getFirstStartTag;
  fn->getFirstEndTag = yhtml_getFirstEndTag;
  fn->getLastStartTag = yhtml_getLastStartTag;
  fn->getLastEndTag = yhtml_getLastEndTag;
  fn->isHtmlEntityStr = yhtml_isHtmlEntityStr;
  fn->skipHtmlEntityStr = yhtml_skipHtmlEntityStr;
  return fn;
}

/*======= exposed through function pointers */
/** Set <code>htmlFn</code>'s <code>strFn</code>.
If <code>strFn</code> is already created, 
this action will be ignored and <code>false</code> will be returned.
<code>htmlFn</code> will NOT free the memory of 
<code>strFn</code> that is created externally, 
you MUST free this yourself.
@param fn the <code>htmlFn</code> to use.
@param strFn pass in the <code>strFn</code> to use.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_setStrFn(htmlFn fn, strFn strFn) {
  if (fn == NULL || fn->d == NULL || strFn == NULL) {
    return false;
  }
  fn->d->strFn = strFn;
  fn->d->setStrFn = true;
  return true;
}

/** Free a <code>htmlFn</code>, free up the memory.
@param fnP pass in the address of <code>htmlFn</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_freeFn(htmlFn *fnP) {
  /* recoverable error */
  if (*fnP == NULL) {return true;}
  if ((*fnP)->d != NULL) {
    /* free strFn ONLY if it is created within library's function */
    if ((*fnP)->d->setStrFn == false
        && (*fnP)->d->strFn != NULL
        && (*fnP)->d->strFn->freeFn(&(*fnP)->d->strFn) == false) {
      /* possible memory leak */
      perror("Error: yhtml_freeFn(htmlFn *) ..(*fnP)->d->strFn->freeFn(&(*fnP)->d->strFn)");
    }
    /* free htmlFnData */
    free((*fnP)->d);
    (*fnP)->d = NULL;
  }
  /* free htmlFn */
  free(*fnP);
  *fnP = NULL;
  return true;
}

/** Get all the content within this HTML tag, 
content is given with pointers to its' start and end position 
inclusive. Note function can NOT return as c string 
since NULL characters is acceptable within content.
Find this tag using various <code>htmlTagMatch</code> method 
i.e. <code>htmFirst, htmLast or htmHug</code>.
Default method is <code>htmFirst</code>.
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find & obtain HTML &lt;title&gt;My Page&lt;/title&gt; 
  tags content, pass <code>tagWord</code> in as "title".
@param contentStart pointer to pointer of the starting character 
  of the content (inclusive), or <code>NULL</code> if failure.
@param contentEnd pointer to pointer of the ending character 
  of the content (inclusive), or <code>NULL</code> if failure.
@param matchMethod indicate to use which <code>htmlTagMatch</code> method
  i.e. <code>htmFirst, htmLast or htmHug</code>.
  Default method is <code>htmFirst</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_getTagCotent(htmlFn fn,
    char *ptr, const unsigned int readLen, const char *tagWord, 
    char **contentStart, char **contentEnd, htmlTagMatch matchMethod) {
  char *tagStartChar, *tagEndChar;
  tagEndChar = NULL;
  /*=== find the starting tag ===*/
  if (matchMethod == htmLast) {
    /* reverse search */
    tagStartChar = yhtml_getLastStartTag(fn, ptr, readLen, 
        tagWord, &tagEndChar);
  } else { /* default: htmFirst & htmHug */
    /* forward search */
    tagStartChar = yhtml_getFirstStartTag(fn, ptr, readLen, 
        tagWord, &tagEndChar);
  }
  if (tagStartChar != NULL && tagEndChar != NULL) {
    /* move pass tag's end char */
    *contentStart = tagEndChar +1;
  } else { /* fail */
    *contentStart = NULL;
    *contentEnd = NULL;
    return false;
  }

  tagEndChar = NULL;
  /*=== find the ending tag ===*/
  if (matchMethod == htmHug) {
    /* reverse search 
      . start from +1 char of tagEndChar, 
        the char of last find (*contentStart),
      . length left doesn't include 
        the char at tagEndChar (*contentStart) */
    tagStartChar = yhtml_getLastEndTag(fn, *contentStart, 
        readLen - (*contentStart - ptr), tagWord, &tagEndChar);
  } else { /* default: htmFirst & htmLast */
    /* forward search
      . start from +1 char of tagEndChar, 
        the char of last find (*contentStart),
      . length left doesn't include 
        the char at tagEndChar (*contentStart) */
    tagStartChar = yhtml_getFirstEndTag(fn, *contentStart, 
        readLen - (*contentStart - ptr), tagWord, &tagEndChar);
  }
  if (tagStartChar != NULL && tagEndChar != NULL) {
    /* move before tag's start char */
    *contentEnd = tagStartChar -1;
  } else { /* fail */
    *contentStart = NULL;
    *contentEnd = NULL;
    return false;
  }
  return true;
}

/** Is tag a HTML comment tag inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
<p>This can be a HTML comment tag, even if we can NOT find 
any HTML comment closing tag before end of character array, 
in this case the tag's last char <code>tagEndChar</code> 
will be set to <code>NULL</code>.
e.g. "  &lt;!-- bla bla bla  " no end tag found with in character array,
however this is still consider a HTML comment tag reaching 
to the end of the character array.
</p>
Those tags located within HTML comment will NOT be ignored!
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
  Note: tagEndChar is set to <code>NULL</code> if HTML comment closing tag
  is NOT found.
@return <code>true</code> if tag is a HTML comment tag 
  otherwise <code>false</code>.
*/
boolean yhtml_isCommentTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    char *charPos, char **tagStartChar, char **tagEndChar) {
  /* pointer to end of array */
  char *ptrEnd = ptr + readLen -1;
  /* MUST test: readLen is at least 7 chars
    includes a char & two arrow bracket e.g. "<!---->" */
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen < 7 
      || charPos < ptr || charPos > ptrEnd) {
    return false;
  }
  /* init */
  *tagStartChar = NULL;
  *tagEndChar = NULL;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_isTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }

  /*--- find HTML comment start tag */
  /* is next char a '!' ?
    to test this current minimum length to end of array is
     6543210
     0123456   charPos char had been tested to within search range
    "<!---->" (must be within search range) */
  if ((charPos + 6) <= ptrEnd 
      && *(charPos + 1) == '!') {
    /* compare, is HTML comment start tag ? "<!--", length is 4 */
    if (memcmp(HTML_COMMEND_START, charPos, HTML_COMMEND_START_LEN) == 0) {
      /* is a HTML comment start tag */
      *tagStartChar = charPos;
    }
  /* is current char a '!' ?
    to test this current minimum length to end of array is
      012345
      543210
    "<!---->" (must be within search range) */
  } else if ((charPos - 1) >= ptr 
      && (charPos + 5) <= ptrEnd 
      && *charPos == '!') {
    /* compare, is HTML comment start tag ? "<!--", length is 4 */
    if (memcmp(HTML_COMMEND_START, charPos -1, HTML_COMMEND_START_LEN) == 0) {
      /* is a HTML comment start tag */
      *tagStartChar = charPos -1;
    }
  } /* else: must do backward search */
  char *ptrTmp;
  if (*tagStartChar == NULL) {
    /* backward search, length NOT inclusive of charPos char */
    ptrTmp = charPos -1;
    while (ptrTmp >= ptr) {
      /* search for HTML comment start tag 
        length inclusive of ptrTmp char */
      ptrTmp = fn->d->strFn->memrchr(ptrTmp, 
          (int) '!', ptrTmp - ptr +1);
      if (ptrTmp == NULL) { 
        /* no HTML comment start tag */
        return false;
      }
      /* found '!', move to '<' char */
      ptrTmp--;
      if (ptrTmp < ptr) {
        /* no HTML comment start tag */
        return false;
      }
      /* compare, is HTML comment start tag ? "<!--", length is 4 */
      if (memcmp(HTML_COMMEND_START, ptrTmp, HTML_COMMEND_START_LEN) == 0) {
        /* is a HTML comment start tag */
        *tagStartChar = ptrTmp;
        break;
      }
      /* try again, minimum possible distance is: "<!--!"
      move back 2 chars to possible '!' position,   012
      since we're at (possible) last "-" char       210 */
      ptrTmp -= 2;
    }
    if (*tagStartChar == NULL) {
      /* no HTML comment start tag */
      return false;
    }
    /* next search start after HTML comment begining tag 
      +4 HTML comment start tag length   01234
      i.e.                              "<!--" */
    ptrTmp += HTML_COMMEND_START_LEN;
  } else {
    /* next search start after HTML comment begining tag 
      +4 HTML comment start tag length   01234
      i.e.                              "<!--" */
    ptrTmp = *tagStartChar + HTML_COMMEND_START_LEN;
  }
  
  /*--- find HTML comment end tag, before charPos position */
  /* is there a closing comment tag between this and our HTML tag? 
  Find this HTML comment closing tag's beginning char.
  . start: search after the last char of HTML comment begining tag
    +4 HTML comment start tag length   01234
    i.e.                              "<!--"
  . length is from start: to charPos char
    but NOT inclusive of charPos char */
  unsigned char lenHtmlEndCommentMinus1 = HTML_COMMEND_END_LEN -1;
  while (ptrTmp + lenHtmlEndCommentMinus1 < charPos) {
    ptrTmp = memchr(ptrTmp, (int) '-', charPos - ptrTmp);
    if (ptrTmp == NULL) { 
      /* there isn't a HTML comment closing tag before charPos position.
      This is a HTML comment tag. 
      Next try to find the HTML comment closing tag before end of array */
      break;
    }
    /* HTML comment closing tag? "-->", length is 3 */
    if (memcmp(HTML_COMMEND_END, ptrTmp, HTML_COMMEND_END_LEN) == 0) {
      /* found the closing HTML comment tag, before charPos position.
        This is not a HTML comment tag. */
      *tagStartChar = NULL;
      return false;
    }
    ptrTmp++; /* move to next char */
  }
  /* after this point, result is no HTML comment closing tag found 
    This is now a HTML comment tag. */
  
  /*--- find HTML comment end tag if possible, 
    after charPos position to end of array */
  /* length is from: charPos to end of array char
    inclusive of charPos char */
  ptrTmp = charPos;
  while (ptrTmp + lenHtmlEndCommentMinus1 <= ptrEnd) {
    ptrTmp = memchr(ptrTmp, (int) '-', ptrEnd - ptrTmp +1);
    if (ptrTmp == NULL || (ptrTmp + lenHtmlEndCommentMinus1) > ptrEnd) { 
      /* there isn't a HTML comment closing tag after charPos position.
      This is still a HTML comment tag. */
      return true;
    }
    /* HTML comment closing tag? "-->", length is 3 */
    if (memcmp(HTML_COMMEND_END, ptrTmp, HTML_COMMEND_END_LEN) == 0) {
      /* found the closing HTML comment tag, after charPos position. 
        position at last char '>' */
      *tagEndChar = ptrTmp + lenHtmlEndCommentMinus1;
      return true;
    }
    ptrTmp++; /* move to next char */
  }
  return true;
}

/** Is tag a start or end tag inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will NOT be ignored!
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *charPos, char **tagStartChar, char **tagEndChar) {
  /* MUST test: readLen is at least 3 chars
    includes a char & two arrow bracket e.g. "<a>" */
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen < 3 
      || charPos < ptr || charPos >= (ptr + readLen)) {
    return false;
  }
  /* init */
  *tagEndChar = NULL;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_isTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }
  /* find tag beginning 
    +1 include ptr char position */
  *tagStartChar = fn->d->strFn->memrchr(charPos, 
      (int) '<', charPos - ptr +1);
  if (*tagStartChar == NULL) { /* not a HTML tag */
    return false;
  }
  /* move to beginning of word pass '<' */
  char *ptrTmp = *tagStartChar +1;
  /* is char a '/' forward slash, '!' OR '?' */
  switch (*ptrTmp) {
  case '/':
  /* assume end tag "</"
    is next char after "</" a '!' OR '?' */
    ptrTmp++;
    switch (*ptrTmp) {
    case '!':
    case '?':
    /* assume end tag "</!" OR "</?"
      + another char to beginning of word */
      ptrTmp++;
    }
    break;
  /* assume start tag '<' 
    is next char after '<' a '!' OR '?' */
  case '!':
  case '?':
    /* assume start tag "<!" OR "<?"
      + another char to beginning of word */
    ptrTmp++;
  }

  /* NB: a valid tag allow no space inbetween beginning of tag's 
  arrow braket and/or slash with its' tag word. i.e. 
  valid start & end tags: e.g.
  "<span class=red>"    "<span class=red >"
  "</span>"             "</span >"
  "<!a>test</!a>"       "<?a>test</?a>"
  "<!a>"                "<?a?>"
  invalid start & end tags: e.g.
  "< span class=red>"   "< span class=red >"
  "< /span>"            "< /span >"
  "</ span>"            "</ span >"
  "< / span>"           "< / span >" 
  "<_a>test</_a>"       "<-a>test</-a>"     "<&a>test</&a>"
  ..
    first char is an alphabet */
  if (isalpha((unsigned char) ptrTmp[0]) == 0) { /* not an alphabet */
    *tagStartChar = NULL;
    return false;
  }
  /* got a valid closing arrow bracket? 
    . start search after tag word's 1st char,
    . length includes ptrTmp position:
      readLen - (ptrTmp - ptr) */
  ptrTmp++;
  *tagEndChar = memchr(ptrTmp, (int) '>', readLen - (ptrTmp - ptr));
  if (*tagStartChar == NULL) { /* not a valid HTML tag */
    return false;
  }
  /* Required check: reverse search, see if we find the beginning tag char 
    +1 to include *tagStartChar position */
  if (fn->d->strFn->memrchr(*tagEndChar, 
      (int) '<', *tagEndChar - *tagStartChar +1) != *tagStartChar) {
     /* not a valid HTML tag, got '<' inside the tag */
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is tag a start tag inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will NOT be ignored!
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
A Starting tag e.g. &lt;title&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isStartTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *tagWord, const char *charPos, 
    char **tagStartChar, char **tagEndChar) {
  /* MUST test: readLen is at least 3 chars
    includes a char & two arrow bracket e.g. "<a>" */
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen < 3 
      || tagWord == NULL || charPos < ptr || charPos >= (ptr + readLen)) {
    return false;
  }
  unsigned int tagWordLen = strlen(tagWord);
  /* MUST test: readLen >= Tag word's length 
    & at least two arrow bracket "<" ">" */
  if (tagWordLen == 0 || tagWordLen +2 >= readLen) {return false;}  

  /* init */
  *tagEndChar = NULL;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_isStartTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }
  /* find tag beginning 
    +1 include ptr char position */
  *tagStartChar = fn->d->strFn->memrchr(charPos, 
      (int) '<', charPos - ptr +1);
  if (*tagStartChar == NULL) { /* not a HTML tag */
    return false;
  }
  /* move to beginning of word, pass '<' */
  char *ptrTmp = *tagStartChar +1;
  /* is char a '!' OR '?' */
  switch (*ptrTmp) {
  case '!':
  case '?':
    /* assume start tag "<!" OR "<?"
      + another char to beginning of word */
    ptrTmp++;
  }
  
  /* NB: a valid tag allow no space inbetween beginning of tag's 
  arrow braket and/or slash with its' tag word. i.e. 
  valid start & end tags: e.g.
  "<span class=red>"    "<span class=red >"
  "</span>"             "</span >"
  "<!a>test</!a>"       "<?a>test</?a>"
  "<!a>"                "<?a?>"
  invalid start & end tags: e.g.
  "< span class=red>"   "< span class=red >"
  "< /span>"            "< /span >"
  "</ span>"            "</ span >"
  "< / span>"           "< / span >" 
  "<_a>test</_a>"       "<-a>test</-a>"     "<&a>test</&a>"
  .. */
  /* compare words */
  if (fn->d->strFn->memcmpIgnoreCase(
      tagWord, ptrTmp, tagWordLen) != 0) { /* not the same */
    *tagStartChar = NULL;
    return false;
  }
  /* got a valid closing arrow bracket? 
    . start search after tag's word,
    . length includes ptrTmp position:
      readLen - (ptrTmp + tagWordLen - ptr) */
  *tagEndChar = memchr(ptrTmp + tagWordLen, (int) '>', 
      readLen - (ptrTmp + tagWordLen - ptr));
  if (*tagStartChar == NULL) { /* not a valid HTML tag */
    return false;
  }
  /* Required check: reverse search, see if we find the beginning tag char 
    +1 to include *tagStartChar position */
  if (fn->d->strFn->memrchr(*tagEndChar, 
      (int) '<', *tagEndChar - *tagStartChar +1) != *tagStartChar) {
     /* not a valid HTML tag, got '<' inside the tag */
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is tag an end tag inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will NOT be ignored!
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
An ending tag e.g &lt;/title&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isEndTag(htmlFn fn,
    const char *ptr, const unsigned long readLen, 
    const char *tagWord, const char *charPos, 
    char **tagStartChar, char **tagEndChar) {
  /* MUST test: readLen is at least 4 chars
    includes a char & two arrow bracket e.g. "</a>" */
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen < 4 
      || tagWord == NULL || charPos < ptr || charPos >= (ptr + readLen)) {
    return false;
  }
  unsigned int tagWordLen = strlen(tagWord);
  /* MUST test: readLen >= Tag word's length 
    & at least two arrow bracket "</" ">" */
  if (tagWordLen == 0 || tagWordLen +3 >= readLen) {return false;}  

  /* init */
  *tagEndChar = NULL;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_isEndTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }
  /* find tag beginning 
    +1 include ptr char position */
  *tagStartChar = fn->d->strFn->memrchr(charPos, 
      (int) '<', charPos - ptr +1);
  if (*tagStartChar == NULL) { /* not a HTML tag */
    return false;
  }
  /* move to beginning of word pass '<' */
  char *ptrTmp = *tagStartChar +1;
  /* char MUST be a '/' forward slash */
  if (*ptrTmp != '/') {
    *tagStartChar = NULL;
    return false;
  }
  /* pass "</", is next char '!' OR '?' */
  ptrTmp++;
  switch (*ptrTmp) {
  case '!':
  case '?':
    /* assume end tag "</!" OR "</?",
      + another char to beginning of word */
    ptrTmp++;
  }

  /* NB: a valid tag allow no space inbetween beginning of tag's 
  arrow braket and/or slash with its' tag word. i.e. 
  valid start & end tags: e.g.
  "<span class=red>"    "<span class=red >"
  "</span>"             "</span >"
  "<!a>test</!a>"       "<?a>test</?a>"
  "<!a>"                "<?a?>"
  invalid start & end tags: e.g.
  "< span class=red>"   "< span class=red >"
  "< /span>"            "< /span >"
  "</ span>"            "</ span >"
  "< / span>"           "< / span >" 
  "<_a>test</_a>"       "<-a>test</-a>"     "<&a>test</&a>"
  .. */
  /* compare words */
  if (fn->d->strFn->memcmpIgnoreCase(
      tagWord, ptrTmp, tagWordLen) != 0) { /* not the same */
    *tagStartChar = NULL;
    return false;
  }
  /* got a valid closing arrow bracket? 
    . start search after tag's word,
    . length includes ptrTmp position:
      readLen - (ptrTmp + tagWordLen - ptr) */
  *tagEndChar = memchr(ptrTmp + tagWordLen, (int) '>', 
      readLen - (ptrTmp + tagWordLen - ptr));
  if (*tagStartChar == NULL) { /* not a valid HTML tag */
    return false;
  }
  /* Required check: reverse search, see if we find the beginning tag char 
    +1 to include *tagStartChar position */
  if (fn->d->strFn->memrchr(*tagEndChar, 
      (int) '<', *tagEndChar - *tagStartChar +1) != *tagStartChar) {
     /* not a valid HTML tag, got '<' inside the tag */
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is tag "in action" 
inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
A starting tag e.g. &lt;title&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    char *charPos, char **tagStartChar, char **tagEndChar) {
  /* assume: yhtml_isStartTag(..) checks its' parameters, 
    NO need to check again. */
  if (yhtml_isTag(fn, ptr, readLen, charPos, 
      tagStartChar, tagEndChar) == false) {
    return false;
  } /* else Tag is OK */
  
  /* is Tag within a HTML comment tag? */
  if (yhtml_isInCommentTag(fn, ptr, readLen, *tagStartChar) == true) {
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is tag a start tag "in action" 
inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
A starting tag e.g. &lt;title&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isStartTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char *charPos, 
    char **tagStartChar, char **tagEndChar) {
  /* assume: yhtml_isStartTag(..) checks its' parameters, 
    NO need to check again. */
  if (yhtml_isStartTag(fn, ptr, readLen, tagWord, charPos, 
      tagStartChar, tagEndChar) == false) {
    return false;
  } /* else Tag is OK */
  
  /* is Tag within a HTML comment tag? */
  if (yhtml_isInCommentTag(fn, ptr, readLen, *tagStartChar) == true) {
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is tag an end tag "in action" 
inside this character array? (XML or HTML)
Provide a pointer to a valid tag's first and last char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
An ending tag e.g &lt;/title&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@param tagStartChar pointer to pointer of the start char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_isEndTagInAction(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char *charPos, 
    char **tagStartChar, char **tagEndChar) {
  /* assume: yhtml_isEndTag(..) checks its' parameters, 
    NO need to check again. */
  if (yhtml_isEndTag(fn, ptr, readLen, tagWord, charPos, 
      tagStartChar, tagEndChar) == false) {
    return false;
  } /* else Tag is OK */
  
  /* is Tag within a HTML comment tag? */
  if (yhtml_isInCommentTag(fn, ptr, readLen, *tagStartChar) == true) {
    *tagStartChar = NULL;
    *tagEndChar = NULL;
    return false;
  }
  return true;
}

/** Is this char located within a HTML comment tag?
Test assume it is anywhere inside the HTML comment tag, 
inclusive of the HTML comment tag itself.
Thus test char includes any character of "<!--" or "-->". 
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param charPos pointer to anywhere within the tag that is to be tested, 
  including tag's start & end chars.
@return <code>true</code> if it lies inside a HTML comment tag 
  otherwise <code>false</code>.
*/
boolean yhtml_isInCommentTag(htmlFn fn,
    char *ptr, const unsigned long readLen, char *charPos) {
  /* MUST test: readLen is at least 7 chars
    includes a char & two arrow bracket e.g. "<!---->" */
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen < 7 
      || charPos < ptr || charPos >= (ptr + readLen)) {
    return false;
  }
  /* init */
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_isInCommentTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }

  /*--- find HTML comment start tag */
  char *ptrEnd = ptr + readLen -1,
    *tagStartChar = NULL;
  /* is next char a '!' ?
    to test this current minimum length to end of array is
     6543210
     0123456   charPos char had been tested to within search range
    "<!---->" (must be within search range) */
  if ((charPos + 6) <= ptrEnd 
      && *(charPos + 1) == '!') {
    /* compare, is HTML comment start tag ? "<!--", length is 4 */
    if (memcmp(HTML_COMMEND_START, charPos, HTML_COMMEND_START_LEN) == 0) {
      /* is a HTML comment start tag */
      tagStartChar = charPos;
    }
  /* is current char a '!' ?
    to test this current minimum length to end of array is
      012345
      543210
    "<!---->" (must be within search range) */
  } else if ((charPos - 1) >= ptr 
      && (charPos + 5) <= ptrEnd 
      && *charPos == '!') {
    /* compare, is HTML comment start tag ? "<!--", length is 4 */
    if (memcmp(HTML_COMMEND_START, charPos -1, HTML_COMMEND_START_LEN) == 0) {
      /* is a HTML comment start tag */
      tagStartChar = charPos -1;
    }
  } /* else: must do backward search */
  char *ptrTmp;
  if (tagStartChar == NULL) {
    /* backward search, length NOT inclusive of charPos char */
    ptrTmp = charPos -1;
    while (ptrTmp >= ptr) {
      /* search for HTML comment start tag 
        length inclusive of ptrTmp char */
      ptrTmp = fn->d->strFn->memrchr(ptrTmp, 
          (int) '!', ptrTmp - ptr +1);
      if (ptrTmp == NULL) { 
        /* no HTML comment start tag */
        return false;
      }
      /* found '!', move to '<' char */
      ptrTmp--;
      if (ptrTmp < ptr) {
        /* no HTML comment start tag */
        return false;
      }
      /* compare, is HTML comment start tag ? "<!--", length is 4 */
      if (memcmp(HTML_COMMEND_START, ptrTmp, HTML_COMMEND_START_LEN) == 0) {
        /* is a HTML comment start tag */
        tagStartChar = ptrTmp;
        break;
      }
      /* try again, minimum possible distance is: "<!--!"
      move back 2 chars to possible '!' position,   012
      since we're at (possible) last "-" char       210 */
      ptrTmp -= 2;
    }
    if (tagStartChar == NULL) {
      /* no HTML comment start tag */
      return false;
    }
    /* next search start after HTML comment begining tag 
      +4 HTML comment start tag length   01234
      i.e.                              "<!--" */
    ptrTmp += HTML_COMMEND_START_LEN;
  } else {
    /* next search start after HTML comment begining tag 
      +4 HTML comment start tag length   01234
      i.e.                              "<!--" */
    ptrTmp = tagStartChar + HTML_COMMEND_START_LEN;
  }
  
  /*--- find HTML comment end tag, before charPos position */
  /* is there a closing comment tag between this and our HTML tag? 
  Find this HTML comment closing tag's beginning char.
  . start: search after the last char of HTML comment begining tag
    +4 HTML comment start tag length   01234
    i.e.                              "<!--"
  . length is from start: to charPos char
    but NOT inclusive of charPos char */
  unsigned char lenHtmlEndCommentMinus1 = HTML_COMMEND_END_LEN -1;
  while (ptrTmp + lenHtmlEndCommentMinus1 < charPos) {
    ptrTmp = memchr(ptrTmp, (int) '-', charPos - ptrTmp);
    if (ptrTmp == NULL) { 
      /* there isn't a HTML comment closing tag before charPos position.
      This is a HTML comment tag. */
      return true;
    }
    /* HTML comment closing tag? "-->", length is 3 */
    if (memcmp(HTML_COMMEND_END, ptrTmp, HTML_COMMEND_END_LEN) == 0) {
      /* found the closing HTML comment tag, before charPos position.
        This is not a HTML comment tag. */
      return false;
    }
    ptrTmp++; /* move to next char */
  }
  /* after this point, result is no HTML comment closing tag found 
    This is now a HTML comment tag. */
  return true;
}

/** Find the first occurance of the HTML start tag,
return a pointer to its' first char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return pointer to the first char of the tag (inclusive), otherwise
  <code>NULL</code>.
*/
char *yhtml_getFirstStartTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar) {
  if (fn == NULL || ptr == NULL || readLen == 0 || tagWord == NULL) {
    return false;
  }
  /* found char's pointer in 1st & 2nd position pointer */
  char *tagStartChar, *found1st, *found2nd, *foundSwap;
  /* last character pointer, 
    -1 not including the extra ptr char position */
  char *ptrEnd = ptr + readLen -1;
  /* remaining length to search */
  unsigned long len1st, len2nd, lenSwap;

  /* start from beginning */
  found1st = ptr;
  found2nd = ptr;
  /* use 1st char of tag word as search character */
  char ch1st, ch2nd, chSwap;
  /* lower case tag word start char */
  ch1st = tolower((unsigned char) tagWord[0]);
  /* upper case tag word start char */
  ch2nd = toupper((unsigned char) tagWord[0]);
  len1st = readLen;
  len2nd = readLen;
  /* find 1st char of tag word 
    use memory function:
    void *memchr(const void *str, int c, size_t n);
    
    Can't use string function because our array 
    is not a c string, it may contain NULLs and 
    does not use NULL as ending character.
    char *strchr(const char *str, int c); */
  found1st = memchr(found1st, (int) ch1st, len1st);
  found2nd = memchr(found2nd, (int) ch2nd, len2nd);
  boolean bSwap = false;
  tagStartChar = NULL;
  do {
    /* found? */
    if (found1st != NULL && found2nd != NULL) {
      /* which one we should look at 1st? */
      if (found2nd < found1st) { /* do a swap */
        bSwap = true;
      } /* else do nothing */
    } else if (found1st == NULL && found2nd != NULL) {
      bSwap = true; /* do a swap */
    } else if (found1st == NULL && found2nd == NULL) {
      break; /* found nothing */
    } /* else if (found1st != NULL && found2nd == NULL) do */

    if (bSwap == true) {
      bSwap = false;
      /* search character */
      chSwap = ch2nd;
      ch2nd = ch1st;
      ch1st = chSwap;
      /* found pointer */
      foundSwap = found2nd;
      found2nd = found1st;
      found1st = foundSwap;
      /* length */
      lenSwap = len2nd;
      len2nd = len1st;
      len1st = lenSwap;
    }
    /* is it the tag? */
    if (yhtml_isStartTagInAction(fn, ptr, readLen, tagWord, found1st, 
        &tagStartChar, tagEndChar) == true) {
      break; /* found */
    } else { /* next search 
        do NOT include previous found1st char position */
      len1st = ptrEnd - found1st;
      /* move 1 char position */
      found1st++;
      /* find 1st char of tag word */
      found1st = memchr(found1st, (int) ch1st, len1st);
    }
  } while (found1st != NULL || found2nd != NULL);
  return tagStartChar;
}

/** Find the first occurance of the HTML end tag,
return a pointer to its' first char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return pointer to the first char of the tag (inclusive), otherwise
  <code>NULL</code>.
*/
char *yhtml_getFirstEndTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar) {
  if (fn == NULL || ptr == NULL || readLen == 0 || tagWord == NULL) {
    return false;
  }
  /* found char's pointer in 1st & 2nd position pointer */
  char *tagStartChar, *found1st, *found2nd, *foundSwap;
  /* last character pointer, 
    -1 not including the extra ptr char position */
  char *ptrEnd = ptr + readLen -1;
  /* remaining length to search */
  unsigned long len1st, len2nd, lenSwap;

  /* start from beginning */
  found1st = ptr;
  found2nd = ptr;
  /* use 1st char of tag word as search character */
  char ch1st, ch2nd, chSwap;
  /* lower case tag word start char */
  ch1st = tolower((unsigned char) tagWord[0]);
  /* upper case tag word start char */
  ch2nd = toupper((unsigned char) tagWord[0]);
  len1st = readLen;
  len2nd = readLen;
  /* find 1st char of tag word 
    use memory function:
    void *memchr(const void *str, int c, size_t n);
    
    Can't use string function because our array 
    is not a c string, it may contain NULLs and 
    does not use NULL as ending character.
    char *strchr(const char *str, int c); */
  found1st = memchr(found1st, (int) ch1st, len1st);
  found2nd = memchr(found2nd, (int) ch2nd, len2nd);
  boolean bSwap = false;
  tagStartChar = NULL;
  do {
    /* found? */
    if (found1st != NULL && found2nd != NULL) {
      /* which one we should look at 1st? */
      if (found2nd < found1st) { /* do a swap */
        bSwap = true;
      } /* else do nothing */
    } else if (found1st == NULL && found2nd != NULL) {
      bSwap = true; /* do a swap */
    } else if (found1st == NULL && found2nd == NULL) {
      break; /* found nothing */
    } /* else if (found1st != NULL && found2nd == NULL) do */

    if (bSwap == true) {
      bSwap = false;
      /* search character */
      chSwap = ch2nd;
      ch2nd = ch1st;
      ch1st = chSwap;
      /* found pointer */
      foundSwap = found2nd;
      found2nd = found1st;
      found1st = foundSwap;
      /* length */
      lenSwap = len2nd;
      len2nd = len1st;
      len1st = lenSwap;
    }
    /* is it the tag? */
    if (yhtml_isEndTagInAction(fn, ptr, readLen, tagWord, found1st, 
        &tagStartChar, tagEndChar) == true) {
      break; /* found */
    } else { /* next search 
        do NOT include previous found1st char position */
      len1st = ptrEnd - found1st;
      /* move 1 char position */
      found1st++;
      /* find 1st char of tag word */
      found1st = memchr(found1st, (int) ch1st, len1st);
    }
  } while (found1st != NULL || found2nd != NULL);
  return tagStartChar;
}

/** Find the last occurance of the HTML start tag,
return a pointer to its' first char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return pointer to the first char of the tag (inclusive), otherwise
  <code>NULL</code>.
*/
char *yhtml_getLastStartTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar) {
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen == 0 
      || tagWord == NULL) {
    return false;
  }
  /* found char's pointer in 1st & 2nd position pointer */
  char *tagStartChar, *found1st, *found2nd, *foundSwap;
  /* last character pointer, 
    -1 not including the extra ptr char position */
  char *ptrEnd = ptr + readLen -1;
  /* remaining length to search */
  unsigned long len1st, len2nd, lenSwap;

  /* start from end */
  found1st = ptrEnd;
  found2nd = ptrEnd;
  /* use 1st char of tag word as search character */
  char ch1st, ch2nd, chSwap;
  /* lower case tag word start char */
  ch1st = tolower((unsigned char) tagWord[0]);
  /* upper case tag word start char */
  ch2nd = toupper((unsigned char) tagWord[0]);
  len1st = readLen;
  len2nd = readLen;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_getLastStartTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }
  /* find 1st char of tag word 
    use memory function:
    strFn's memrchr i.e.
    void *(*memrchr)(const void *ptrEnd, int c, size_t n);
    
    Can't use string function because our array 
    is not a c string, it may contain NULLs and 
    does not use NULL as ending character.
    char *strrchr(const char *str, int c); */
  found1st = fn->d->strFn->memrchr(found1st, (int) ch1st, len1st);
  found2nd = fn->d->strFn->memrchr(found2nd, (int) ch2nd, len2nd);
  boolean bSwap = false;
  tagStartChar = NULL;
  do {
    /* found? */
    if (found1st != NULL && found2nd != NULL) {
      /* which one we should look at 1st? */
      if (found2nd > found1st) { /* do a swap */
        bSwap = true;
      } /* else do nothing */
    } else if (found1st == NULL && found2nd != NULL) {
      bSwap = true; /* do a swap */
    } else if (found1st == NULL && found2nd == NULL) {
      break; /* found nothing */
    } /* else if (found1st != NULL && found2nd == NULL) do */

    if (bSwap == true) {
      bSwap = false;
      /* search character */
      chSwap = ch2nd;
      ch2nd = ch1st;
      ch1st = chSwap;
      /* found pointer */
      foundSwap = found2nd;
      found2nd = found1st;
      found1st = foundSwap;
      /* length */
      lenSwap = len2nd;
      len2nd = len1st;
      len1st = lenSwap;
    }
    /* is it the tag? */
    if (yhtml_isStartTagInAction(fn, ptr, readLen, tagWord, found1st, 
        &tagStartChar, tagEndChar) == true) {
      break; /* found */
    } else { /* next search 
        do NOT include previous found1st char position */
      len1st = found1st - ptr;
      /* move 1 char position */
      found1st--;
      /* find 1st char of tag word */
      found1st = fn->d->strFn->memrchr(found1st, (int) ch1st, len1st);
    }
  } while (found1st != NULL || found2nd != NULL);
  return tagStartChar;
}

/** Find the last occurance of the HTML end tag,
return a pointer to its' first char (inclusive).
Those tags located within HTML comment will be ignored.
e.g. &lt;!-- &lt;title&gt;My Page ignored&lt;/title&gt; --&gt;
@param fn the <code>htmlFn</code> to use.
@param ptr the pointer to the character array 
  that is going to be search.
@param readLen the length of the array to be read.
@param tagWord the tag's word to be searched, 
  e.g. to find HTML &lt;title&gt; OR &lt;/title&gt; 
  tag, pass <code>tagWord</code> in as "title".
@param tagEndChar pointer to pointer of the end char of the found tag 
  (inclusive), otherwise <code>NULL</code>.
@return pointer to the first char of the tag (inclusive), otherwise
  <code>NULL</code>.
*/
char *yhtml_getLastEndTag(htmlFn fn,
    char *ptr, const unsigned long readLen, 
    const char *tagWord, char **tagEndChar) {
  if (fn == NULL || fn->d == NULL || ptr == NULL || readLen == 0 
      || tagWord == NULL) {
    return false;
  }
  /* found char's pointer in 1st & 2nd position pointer */
  char *tagStartChar, *found1st, *found2nd, *foundSwap;
  /* last character pointer, 
    -1 not including the extra ptr char position */
  char *ptrEnd = ptr + readLen -1;
  /* remaining length to search */
  unsigned long len1st, len2nd, lenSwap;

  /* start from end */
  found1st = ptrEnd;
  found2nd = ptrEnd;
  /* use 1st char of tag word as search character */
  char ch1st, ch2nd, chSwap;
  /* lower case tag word start char */
  ch1st = tolower((unsigned char) tagWord[0]);
  /* upper case tag word start char */
  ch2nd = toupper((unsigned char) tagWord[0]);
  len1st = readLen;
  len2nd = readLen;
  if (fn->d->strFn == NULL) {
    if (yhtml_initStrFn(fn) == false) {
      /* unrecoverable error library function end the program */
      FN_OOPS("Error: yhtml_getLastEndTag(..) yhtml_initStrFn(fn) == false; should NOT continue")
    }
  }
  /* find 1st char of tag word 
    use memory function:
    strFn's memrchr i.e.
    void *(*memrchr)(const void *ptrEnd, int c, size_t n);
    
    Can't use string function because our array 
    is not a c string, it may contain NULLs and 
    does not use NULL as ending character.
    char *strrchr(const char *str, int c); */
  found1st = fn->d->strFn->memrchr(found1st, (int) ch1st, len1st);
  found2nd = fn->d->strFn->memrchr(found2nd, (int) ch2nd, len2nd);
  boolean bSwap = false;
  tagStartChar = NULL;
  do {
    /* found? */
    if (found1st != NULL && found2nd != NULL) {
      /* which one we should look at 1st? */
      if (found2nd > found1st) { /* do a swap */
        bSwap = true;
      } /* else do nothing */
    } else if (found1st == NULL && found2nd != NULL) {
      bSwap = true; /* do a swap */
    } else if (found1st == NULL && found2nd == NULL) {
      break; /* found nothing */
    } /* else if (found1st != NULL && found2nd == NULL) do */

    if (bSwap == true) {
      bSwap = false;
      /* search character */
      chSwap = ch2nd;
      ch2nd = ch1st;
      ch1st = chSwap;
      /* found pointer */
      foundSwap = found2nd;
      found2nd = found1st;
      found1st = foundSwap;
      /* length */
      lenSwap = len2nd;
      len2nd = len1st;
      len1st = lenSwap;
    }
    /* is it the tag? */
    if (yhtml_isEndTagInAction(fn, ptr, readLen, tagWord, found1st, 
        &tagStartChar, tagEndChar) == true) {
      break; /* found */
    } else { /* next search 
        do NOT include previous found1st char position */
      len1st = found1st - ptr;
      /* move 1 char position */
      found1st--;
      /* find 1st char of tag word */
      found1st = fn->d->strFn->memrchr(found1st, (int) ch1st, len1st);
    }
  } while (found1st != NULL || found2nd != NULL);
  return tagStartChar;
}

/** Test if this is an Entity string. 
i.e. a HTML 4.01 Entities Name or Entities Number.
<p>NOTE: this function does NOT check an exact 
HTML 4.01 Entities Name or Entities Number.
If it start with '&', ends with ';' and has alphabets 
inbetween, then this is assume to be an Entities Name.
If it start with "&#", ends with ';' and has digits
inbetween, then this is assume to be an Entities Number.
</p>
<br/>Entities Name e.g. &nbsp; is the non-breaking space.
<br/>Entity Number e.g. &#34; is the double quote.
@param str the string.
@param strLen the length of the string to be check to.
@return Return none zero or the length of the Entity string. 
  Return zero if it is NOT an Entity string.
*/
unsigned char yhtml_isHtmlEntityStr(const char *str, 
    const unsigned int strLen) {
  unsigned char len = yhtml_skipHtmlEntityStr(str, strLen);
  if (len < HTML_ENTITY_STR_MIN_LEN) {
    return 0;
  }
  return len;
}

/** Number of place to skip. If this is an Entity string, 
i.e. a HTML 4.01 Entities Name or Entities Number,
place to skip is the Entity string length. 
<p>If string starts with "&#" but NOT an Entity string 
(as defined in this function), place to skip is 2.
If string starts with '&' but NOT an Entity string 
(as defined in this function), place to skip is 1.
If string does NOT start with "&#" or '&', place to skip is 0.
</p>
<p>NOTE: this function does NOT check an exact 
HTML 4.01 Entities Name or Entities Number.
If it start with '&', ends with ';' and has alphabets 
inbetween, then this is assume to be an Entities Name.
If it start with "&#", ends with ';' and has digits
inbetween, then this is assume to be an Entities Number.
</p>
<br/>Entities Name e.g. &nbsp; is the non-breaking space.
<br/>Entity Number e.g. &#34; is the double quote.
@param str the string.
@param strLen the length of the string to be check to.
@return Return none zero or the number of place to skip 
  if it is an Entity string. 
  Return zero if it is NOT an Entity string.
*/
unsigned char yhtml_skipHtmlEntityStr(const char *str, 
    const unsigned int strLen) {
  /*  http://www.w3schools.com/tags/ref_entities.asp
  HTML 4.01 Entities Name & Entities Number
  Entities Name - longest length sample "&plusmn;"
  Entity Number - longest length sample "&#8364;" 
  i.e.  "12345678"
        "01234567"
        "&plusmn;"
        "&#8364;" 
  */
  if (str == NULL || strLen == 0) {return 0;}
  unsigned char i, endCharPos;

  if (strLen == 1) {
    if (str[0] == '&') {
      return 1;
    }
    return 0;
  }
  endCharPos = 0;   /* End char ';' have NOT been found */
  /*--- Entity Number? ---*/
  if (str[2] == '#') {
    if (strLen > HTML_ENTITY_NUM_MAX_LEN) {
      i = HTML_ENTITY_NUM_MAX_LEN - 1;
    } else {
      i = strLen - 1;
    }
    /* 1 is the position of # */
    while (i > 1) {
      /* have found end char ';' */
      if (endCharPos > 0) {
        /* test for existen of digit (0 to 9) */
        if (!isdigit((unsigned char) str[i])) {
          /* this isn't a HTML Entity Number! */
          endCharPos = 0;
          break;
        }
      /* is end char? */
      } else if (str[i] == ';') {
        endCharPos = i;
      }
      i--;
    }
    if (endCharPos == 0) {
      return 2; /* skip 2 position of "&#" */
    }
    return endCharPos +1; /* +1 to skip pass ';' position */
  }
  /*--- Entity Name? ---*/
  if (strLen > HTML_ENTITY_NAME_MAX_LEN) {
    i = HTML_ENTITY_NAME_MAX_LEN - 1;
  } else {
    i = strLen - 1;
  }
  /* 0 is the position of '&' */
  while (i > 0) {
    /* have found end char ';' */
    if (endCharPos > 0) {
      /* test for existen of letters (A to Z or a to z) */
      if (!isalpha((unsigned char) str[i])) {
        /* this isn't a HTML Entity Name! */
        endCharPos = 0;
        break;
      }
    /* is end char? */
    } else if (str[i] == ';') {
      endCharPos = i;
    }
    i--;
  }
  if (endCharPos == 0) {
    return 1; /* skip 1 position of "&" */
  }
  return endCharPos +1; /* +1 to skip pass ';' position */
}

/*======= function ONLY for internal use */
/** Initialize <code>htmlFn</code>'s <code>strFn</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param fn the <code>htmlFn</code> to use.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yhtml_initStrFn(htmlFn fn) {
  fn->d->strFn = ystr_createFn();
  if (fn->d->strFn == NULL) {
    perror("Error: yhtml_initStrFn(htmlFn) fn->d->strFn == NULL");
    return false;
  }
  return true;
}
